package ClassAlgebra;

import HierarchyPackage.Hierarchy;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author Martin Haulrich
 */
public class ProbAlgebraSingleton {

    Map<ProbExpr, ProbExpr> cache;

    protected ProbAlgebraSingleton() {
        cache = new LinkedHashMap<ProbExpr, ProbExpr>(12800, 0.75F, true) {

            @Override
            protected boolean removeEldestEntry(Map.Entry<ProbExpr, ProbExpr> eldest) {
                return size() > 10000;
            }
        };

    }

    private static class PAHolder {

        private static final ProbAlgebraSingleton INSTANCE = new ProbAlgebraSingleton();

    }

    public static ProbAlgebraSingleton getInstance() {
        return PAHolder.INSTANCE;
    }

    public ProbExpr map(ProbExpr e) {
        return map(e, null);

    }

    public ProbExpr map(ProbExpr e, Hierarchy hier) {



        //System.out.println(e);
        ProbExpr ne = new ZeroProbExpr();
        //  System.err.println("-----------");
        // Keep trying to reduce the expression as long as changes are made
        while (!e.equals(ne)) {

            //     System.err.println(e);
            /**
             * @TODO Implement none-overriding clone to avoid the exception-handling
             */
//            try {
//                ne = (ProbExpr) e.clone();
//            } catch (CloneNotSupportedException ex) {
//                Logger.getLogger(SuffixEstimatorExp.class.getName()).log(Level.SEVERE, null, ex);
//            }
            ne = e;
            // Reduce the propability-expression
            e = propMap(e, hier);

            // Reduce the class-expressions in the probability-expression
            e = recClassMap(e, hier);
        }
        //  System.err.println("-----------");


        return e;
    }

    public ProbExpr recClassMap(ProbExpr e, Hierarchy hier) {



        if (e instanceof SimpleProbExpr) {
            SimpleProbExpr se = (SimpleProbExpr) e;
            // return new SimpleProbExpr(ClassAlgebra.map(se.e, hier));
            return new SimpleProbExpr(ClassAlgebraSingleton.getInstance().map(se.e, hier));
        }
        if (e instanceof ComplexProbExpr) {
            ComplexProbExpr ce = (ComplexProbExpr) e;
            return new ComplexProbExpr(recClassMap(ce.pe1, hier), recClassMap(ce.pe2, hier), ce.operation);
        }

        return e;
    }

    private ProbExpr propMap(ProbExpr e, Hierarchy hier) {


        //System.out.println(e);
        ProbExpr ee = cache.get(e);
        //ProbExpr ee = null;
        ProbExpr orig = e;

//        System.out.println("map");
//        if (ee != null) {
//            System.out.println("    hit");
//        } else {

        if (ee == null) {
            ProbExpr ne = new ZeroProbExpr();

            while (!e.equals(ne)) {

                /**
                 * @TODO Implement none-overriding clone to avoid the exception-handling
                 */
//            try {
//                ne = (ProbExpr) e.clone();
//            } catch (CloneNotSupportedException ex) {
//                Logger.getLogger(SuffixEstimatorExp.class.getName()).log(Level.SEVERE, null, ex);
//            }
                ne = e;
                if (e instanceof SimpleProbExpr) {
                    e = propMapSimple((SimpleProbExpr) e, hier);

                }
                if (e instanceof ComplexProbExpr) {
                    e = propMapComplex((ComplexProbExpr) e, hier);
                }
            }
            ee = e;
            cache.put(orig, ee);
        }
        return ee;
    }

    private ProbExpr propMapComplex(ComplexProbExpr ce, Hierarchy hier) {

        if (ce.operation == ComplexProbExpr.DIFFERENCE) {
            if (ce.pe1.equals(ce.pe2)) {
                return new ZeroProbExpr();
            }
            if (ce.pe2 instanceof ZeroProbExpr) {
                return ce.pe1;
            }
        }
        if (ce.operation == ComplexProbExpr.SUM) {
            if (ce.pe2 instanceof ZeroProbExpr) {
                return ce.pe1;
            }
            if (ce.pe1 instanceof ZeroProbExpr) {
                return ce.pe2;
            }
        }

        ProbExpr p1 = propMap(ce.pe1, hier);
        ProbExpr p2 = propMap(ce.pe2, hier);
        ProbExpr e = new ComplexProbExpr(p1, p2, ce.operation);
        return e;
    }

    private ProbExpr propMapSimple(SimpleProbExpr se, Hierarchy hier) {
        ProbExpr e = se;

        Expression ce = se.e;

        if (ce.type() == Expression.EMPTY) {
            return new ZeroProbExpr();
        }

        if (ce.type() == Expression.COMPLEX) {
            ComplexExpression cce = (ComplexExpression) ce;

            if (cce.operation == ComplexExpression.DIFFERENCE) {
                ProbExpr p1 = new SimpleProbExpr(cce.e1);
                /**
                 * @TODO We try and reduce the intersection-expression as soon as they are made. There is some inefficiency in this becuase we will later try again
                 */
                //Expression cc2 = ClassAlgebra.map(new ComplexExpression(ComplexExpression.INTERSECTION, cce.e1, cce.e2), hier);
                Expression cc2 = ClassAlgebraSingleton.getInstance().map(new ComplexExpression(ComplexExpression.INTERSECTION, cce.e1, cce.e2), hier);
                ProbExpr p2 = new SimpleProbExpr(cc2);
                e = propMap(new ComplexProbExpr(p1, p2, ComplexProbExpr.DIFFERENCE), hier);
            }
            // F*(a ∪ b) = F*(a) + F*(b) - F*(a ∩ b);
            if (cce.operation == ComplexExpression.UNION) {
                ProbExpr p1 = new SimpleProbExpr(cce.e1);
                ProbExpr p2 = new SimpleProbExpr(cce.e2);
                //Expression cc2 = ClassAlgebra.map(new ComplexExpression(ComplexExpression.INTERSECTION, cce.e1, cce.e2), hier);
                Expression cc2 = ClassAlgebraSingleton.getInstance().map(new ComplexExpression(ComplexExpression.INTERSECTION, cce.e1, cce.e2), hier);
                ProbExpr p3 = new SimpleProbExpr(cc2);

                ProbExpr sume = propMap(new ComplexProbExpr(p1, p2, ComplexProbExpr.SUM), hier);
                e = propMap(new ComplexProbExpr(sume, p3, ComplexProbExpr.DIFFERENCE), hier);
            }
        }

        if (ce.type() == Expression.LIST) {
            ListExpression lce = (ListExpression) ce;

            return mapList(lce, hier);

        }
        return e;
    }

    private ProbExpr mapList(ListExpression lce, Hierarchy hier) {

        if (lce.operation == ListExpression.UNION) {
            // Making this copy might not be necessary
            Set<Expression> le = new HashSet<Expression>(lce.expList);

            // Heuristic: remove all empty expression from list
            for (Iterator<Expression> it = le.iterator(); it.hasNext();) {
                Expression ite = it.next();
                if (ite.type() == Expression.EMPTY) {
                    it.remove();
                }
            }



            if (hier != null) {
                List<Expression> li = new LinkedList<Expression>(le);
                for (int i = 0; i < li.size() - 1; i++) {
                    if (li.get(i).type() == Expression.SIMPLE) {
                        SimpleExpression si = (SimpleExpression) li.get(i);
                        for (int j = i + 1; j < li.size(); j++) {
                            if (li.get(j).type() == Expression.SIMPLE) {
                                SimpleExpression sj = (SimpleExpression) li.get(j);
                                if (hier.superclassOf(si.c, sj.c)) {
                                    le.remove(sj);

                                }
                                if (hier.superclassOf(sj.c, si.c)) {
                                    le.remove(si);

                                }
                            }
                        }
                    }
                }

            }

            if (le.isEmpty()) {
                return new ZeroProbExpr();
            }
            // F*(⋃[a1...an) = F*(a1) + F*(⋃[a2...an]) - F*(a1 ∩ ⋃[a2...an])
////
////            // Not pretty - consider alternative
//            Expression head = le.iterator().next();
//            //List<Expression> tail = le.subList(1, le.size());
//            le.remove(head);
//            Expression tailExp = new ListExpression(ListExpression.UNION, le);//ClassAlgebra.map(new ListExpression(ListExpression.UNION, le), hier);
//            Expression diffExp = ClassAlgebra.map(new ComplexExpression(ComplexExpression.INTERSECTION, head, tailExp), hier);
//            //System.err.println("TailExp: " + tailExp);
//            ProbExpr e1 = new SimpleProbExpr(head);
//            ProbExpr e2 = new SimpleProbExpr(tailExp);
//            ProbExpr e3 = new SimpleProbExpr(diffExp);
//            ProbExpr front = new ComplexProbExpr(e1, e2, ComplexProbExpr.SUM);
//            ProbExpr all = new ComplexProbExpr(front, e3, ComplexExpression.DIFFERENCE);
//            return propMap(all, hier);
            return mapUnionList(new ArrayList<Expression>(le), hier);
        }

        if (lce.operation == ListExpression.INTERSECTION) {
            throw new UnsupportedOperationException("Where did an intersection-list come from???\nHandling of these is not implemented");
        }

        return new SimpleProbExpr(lce);
    }

    private ProbExpr mapUnionList(List<Expression> le, Hierarchy hier) {

        // F*(⋃[a1...an) = F*(a1) + F*(⋃[a2...an]) - F*(a1 ∩ ⋃[a2...an])

        if (le.size() == 1) {
            return new SimpleProbExpr(le.get(0));
        }

        Expression head = le.get(0);

        List<Expression> tailList = le.subList(1, le.size());


        ProbExpr pHead = new SimpleProbExpr(head);
        ProbExpr pTail = mapUnionList(tailList, hier);

        Expression tailExp = new ListExpression(ListExpression.UNION, tailList);
        //ProbExpr pTail = new SimpleProbExpr(tailExp);
        //Expression diffExp = ClassAlgebra.map(new ComplexExpression(ComplexExpression.INTERSECTION, head, tailExp), hier);
        Expression diffExp = ClassAlgebraSingleton.getInstance().map(new ComplexExpression(ComplexExpression.INTERSECTION, head, tailExp), hier);
        ProbExpr pDiff = new SimpleProbExpr(diffExp);

        ProbExpr front = new ComplexProbExpr(pHead, pTail, ComplexProbExpr.SUM);
        ProbExpr all = new ComplexProbExpr(front, pDiff, ComplexExpression.DIFFERENCE);
        return propMap(all, hier);



    }
}
